package com.lyr.query.dynamic.core.handler;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lyr.query.dynamic.core.domain.DynamicQueryProperties;
import com.lyr.query.dynamic.core.domain.DynamicQueryProperties.EnableSaveConfig;
import com.lyr.query.dynamic.core.domain.QueryCondition;
import com.lyr.query.dynamic.core.domain.QueryConfig;
import com.lyr.query.dynamic.core.enums.DynamicCondition;
import com.lyr.query.dynamic.core.service.ConfigService;
import com.lyr.query.dynamic.core.service.QueryConfigService;
import com.lyr.query.dynamic.core.utils.SqlUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.context.ApplicationContext;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

/**
 * mubatis拦截器  拦截sql的执行，拼接动态sql条件
 *
 * @author zk
 */

@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),})
public class MybatisInterceptor implements Interceptor {

    private final ApplicationContext applicationContext;
    private final ConfigService configService;
    private final DynamicQueryProperties properties;
    private final HttpServletRequest request;

    public MybatisInterceptor(ConfigService configService, DynamicQueryProperties properties, HttpServletRequest request,ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        this.properties = properties;
        this.configService = configService;
        this.request = request;
    }

    /**
     * 进行拦截的时候要执行的方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    @SuppressWarnings("all")
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement)args[0];
        Object parameter = args[1];
        if (Objects.isNull(parameter)){
            return invocation.proceed();
        }
        RowBounds rowBounds = (RowBounds)args[2];
        ResultHandler resultHandler = (ResultHandler)args[3];
        Executor executor = (Executor)invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;

        //由于逻辑关系，只会进入一次
        if (args.length == 4) {
            //4 个参数时
            boundSql = mappedStatement.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey)args[4];
            boundSql = (BoundSql)args[5];
        }

        //id为执行的mapper类的全路径名，如com.metro.dao.UserMapper
        String id = mappedStatement.getId();
        //methodId为执行的mapper方法的全路径名，如com.metro.dao.UserMapper.selectByPrimaryKey
        String methodId = mappedStatement.getId();
        //反射获取方法
        String methodName = methodId.substring(methodId.lastIndexOf(".") + 1);
        //获取到类
        Class<?> clazz = Class.forName(id.substring(0, id.lastIndexOf(".")));
        //根据方法名获取到类当中的方法
        Method method = Arrays.stream(clazz.getMethods()).filter(m -> m.getName().equals(methodName)).findFirst().orElse(null);
        if (Objects.isNull(method)) {
            return invocation.proceed();
        }
        //判断是否为单参数
        boolean isSingleParam = method.getParameterCount() == 1;

        // 遍历方法参数，找到@DynamicCondition注解  或 List<QueryCondition> 类型 的参数
        Parameter[] parameters = method.getParameters();
        if (!isSingleParam){
            //如果不是单参数，则先进行排序，避免key生成不对
            Arrays.sort(parameters, Comparator.comparingInt(Parameter::getModifiers));
        }
        List<QueryCondition> conditions = Collections.emptyList();
        DynamicCondition annotation = null;

        for (int i = 0; i < parameters.length; i++) {
            String key = getParamKey(parameters[i], i, isSingleParam);
            Object object;
            try {
                Map<?, ?> parameter1 = (Map<?, ?>)parameter;
                object = ((Map<?, ?>)parameter).get(key);
            } catch (Exception e) {
                // 无法转换，则跳出循环
                break;
            }
            //通过类型判断，是否为动态条件
            if (object instanceof List<?>) {
                List<?> list = (List<?>)object;
                // 判断是否所有元素都是 QueryCondition 类型
                if (list.stream().allMatch(QueryCondition.class::isInstance)) {
                    conditions = (List<QueryCondition>)object;
                    annotation = parameters[i].getAnnotation(DynamicCondition.class);
                }
            }
        }
        // 判断条件是否为空, 如果为空则不进行修改
        if (CollectionUtils.isEmpty(conditions)) {
            return invocation.proceed();
        }
        //处理条件，如果存在别名则加上别名
        String alias = Optional.ofNullable(annotation).map(DynamicCondition::value).orElse("");
        String sqlWhere = SqlUtils.getSqlWhereClause(conditions, alias, clazz);
        //获取到原始sql语句
        String sql = boundSql.getSql();
        sql = SqlUtils.concatSqlWhereClause(sql, sqlWhere);
        //通过反射修改boundSql对象的sql语句
        Field field = ReflectionUtils.findField(boundSql.getClass(), "sql");
        if (Objects.nonNull(field)) {
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, boundSql, sql);
        }
        //执行修改后的sql语句
        List<Object> list = null;
        try {
            list = executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        } catch (SQLException e) {
            log.error("》》》执行更改的SQL出错 sql : {}", sql, e);
            log.info("》》》改为执行原SQL语句 ");
            return invocation.proceed();
        }
        EnableSaveConfig config = properties.getSaveConfig();
        if (ObjectUtils.isNull(config)){
            return list;
        }
        if(Objects.nonNull(config.getEnable()) && config.getEnable()){
            //判断用户是否实现了QueryConfigService，是否存在这个类型的bean
            if (applicationContext.getBeanNamesForType(QueryConfigService.class).length == 0) {
                log.warn("》》》尚未实现QueryConfigService接口  getUserId()  方法，无法获取用户信息，保存查询条件失败");
                return list;
            }
            //获取到QueryConfigService的bean
            QueryConfigService queryConfigService = applicationContext.getBean(QueryConfigService.class);
            //保存查询条件，提供根据用户id查询查询条件的接口
            Long userId = queryConfigService.getUserId();
            QueryConfig queryConfig = new QueryConfig();
            queryConfig.setUserId(userId);
            queryConfig.setConditionsJson(conditions);
            if (config.getTenantMode()){
                try {
                    Method getTenantId = queryConfigService.getClass().getDeclaredMethod("getTenantId", Long.class);
                    queryConfig.setTenantId(queryConfigService.getTenantId());
                } catch (NoSuchMethodException e) {
                    log.warn("》》》尚未实现QueryConfigService接口  getTenantId()  方法，无法获取租户信息，保存查询条件失败");
                    return list;
                }
            }
            queryConfig.setPath(request.getRequestURI());
            QueryConfig one = configService.lambdaQuery().eq(QueryConfig::getUserId, userId).eq(QueryConfig::getPath, request.getRequestURI()).one();
            if (Objects.nonNull(one)){
                queryConfig.setId(one.getId());
                queryConfig.setUpdateTime(LocalDateTime.now());
                configService.updateById(queryConfig);
                return list;
            }
            queryConfig.setCreateTime(LocalDateTime.now());
            configService.save(queryConfig);
        }
        return list;
    }

    /**
     * 获取参数在Map中的Key（支持@Param或param1格式）
     */
    private String getParamKey(Parameter parameter, int index, boolean isSingleParam) {
        // 1. 优先检查 @Param 注解
        if (parameter.isAnnotationPresent(Param.class)) {
            return parameter.getAnnotation(Param.class).value();
        }
        // 2. 如果是单个参数且编译保留参数名，直接使用参数名
        if (isSingleParam) {
            return parameter.getName();
        }
//        // 3. 默认返回 param1
//        return "param" + (index + 1);
        //判断
        if (parameter.getType().equals(Page.class)){
            return "ew";
        }
        return "query";
    }
}

